#include "clienthandler.h"


#define PORT ("1234")
#define BUFLEN (255)

ClientHandler::ClientHandler(MySocket &sock, DataBase &db, struct in_addr addr) : m_sock(sock), m_db(db)
{
    m_ip = (char*)malloc(INET_ADDRSTRLEN);

    if(!inet_ntop(AF_INET, &addr, m_ip, INET_ADDRSTRLEN)) // We get the client ip as an ipv4 addr and check if we get it correctly.
    {
        throw SocketException("Failed to get ip address as an ipv4.", GET_ERROR);
    }
}

ClientHandler::ClientHandler(MySocket &sock, DataBase &db, struct in6_addr addr) : m_sock(sock), m_db(db)
{
    m_ip = (char*)malloc(INET6_ADDRSTRLEN);

    if(!inet_ntop(AF_INET6, &addr, m_ip, INET6_ADDRSTRLEN)) // We get the client ip as an ipv6 addr and check if we get it correctly.
    {
        throw SocketException("Failed to get ip address as an ipv6.", GET_ERROR);
    }
}

ClientHandler::~ClientHandler()
{
    logout();
    free(m_ip);
}

void ClientHandler::run()
{
    bool connected = false;
    Message message;

    /* Login du client */
    try
    {
        Identification id;
        m_sock.read(&message, sizeof(Message));
        id = message.content.id;
        switch(message.type)
        {
        case 'L':
            if(login(id)) // si le login password est valide
            {
                m_username = id.username;

                message.type = 'O';
                m_sock.write(&message, sizeof(Message));

                connected = true;
            }
            else
            {
                message.type = 'Q';
                m_sock.write(&message, sizeof(Message));
            }
            break;
        case 'l':
            if(createUser(id) && login(id)) // si on a pu créer et connecter l'utilisateur
            {
                m_username = id.username;

                message.type = 'O';
                m_sock.write(&message, sizeof(Message));

                connected = true;
            }
            else
            {
                message.type = 'Q';
                m_sock.write(&message, sizeof(Message));
            }
            break;
        default:
            message.type = 'Q';
            m_sock.write(&message, sizeof(Message));
            break;
        }
    }
    catch (DBException e) // si l'erreur vient de la bd on signale au client que sa connection à échouée
    {
        std::cout<<"Database error during client login. "<<e.what()<<std::endl;
        connected = false;

        message.type = 'Q';
        try
        {
            m_sock.write(&message, sizeof(Message));
        }
        catch (std::exception e)
        {
        }
    }
    catch (SocketException e)
    {
        std::cout<<"Socket error during client login. "<<e.what()<<std::endl;
        connected = false;
    }
    catch (std::exception e)
    {
        std::cout<<"Error during client login. "<<e.what()<<std::endl;
        connected = false;
    }

    /* Interpretation des requetes client */
    try
    {
        while(connected)
        {
            m_sock.read(&message,sizeof(Message));

            switch(message.type)
            {
            case 'E':
                getFileList(message.content.nbFiles);
                break;
            case 'A':
                sendFileList();
                break;
            case 'D':
                getDescription(message.content.file.name, message.content.file.author);
                break;
            case 'C':
                getConnection(message.content.file.name, message.content.file.author);
                break;
            case 'Q':
                connected = false;
                break;
            case 'K':
                search();
                break;
            default:
                break;
            }
        }
    }
    catch (std::exception e)
    {
        std::cout<<"Error during communication with client "<<m_username.toStdString()<<". "<<e.what()<<std::endl;
    }

    try
    {
        logout();
    }
    catch (std::exception e)
    {
        std::cout<<"Error during logout of client "<<m_username.toStdString()<<". "<<e.what()<<std::endl;
    }

    quit();
}



void ClientHandler::search()
{
    char *mot;
    uint32_t size;
    m_sock.read(&size,sizeof(uint32_t));
    mot=new char[size+1];
    mot[size] = '\0';
    m_sock.read(mot,size*sizeof(char));

    QSqlQuery searchCount = m_db.prepareQuery("SELECT COUNT(*) from files where desc like :mot1 or name like :mot2");
    searchCount.bindValue(":mot1", mot);
    searchCount.bindValue(":mot2", mot);
    QSqlQuery searchQuery = m_db.prepareQuery("SELECT name, username, size from files where desc like :mot1 or name like :mot2");
    searchQuery.bindValue(":mot1", mot);
    searchQuery.bindValue(":mot2", mot);

    if(!searchCount.exec())
    {
        throw DBException("Error during file counting", m_db.lastError());
    }
    searchCount.next();

    if(!searchQuery.exec())
    {
        throw DBException("Error during file accessing", m_db.lastError());
    }


    // On envoie le nombre de fichiers qui vont être envoyés au client
    Message message;
    message.type = 'E';
    message.content.nbFiles = searchCount.value(0).toInt();

    m_sock.write(&message, sizeof(Message));

    // On envoie les fichiers
    while(searchQuery.next())
    {
        FileDescServ file;
        strcpy(file.name, searchQuery.value(0).toString().toStdString().c_str());
        strcpy(file.author, searchQuery.value(1).toString().toStdString().c_str());
        file.size = searchQuery.value(2).toInt();

        m_sock.write(&file, sizeof(FileDescServ));
    }
    delete(mot);


}


/**
 * @brief Récupère une connection pour le client et lui renvoie
 */
void ClientHandler::getConnection(char *name, char *author)
{
    Message message;
    QSqlQuery connectionQuery = m_db.prepareQuery("SELECT ip, port from connexion where username=:username");
    connectionQuery.bindValue(":username", author);

    if(!connectionQuery.exec())
    {
        message.type = 'i';
    }
    else
    {
        connectionQuery.next();
        message.type = 'I';
        strcpy(message.content.connection.ip, connectionQuery.value(0).toString().toStdString().c_str());
        strcpy(message.content.connection.port, connectionQuery.value(1).toString().toStdString().c_str());
    }

    m_sock.write(&message, sizeof(Message));
}

/**
 * @brief Récupère une description pour le client et lui renvoie
 */
void ClientHandler::getDescription(char *name, char *author)
{
    QSqlQuery descriptionQuery = m_db.prepareQuery("SELECT desc from files where username=:username AND name=:name");
    descriptionQuery.bindValue(":username", author);
    descriptionQuery.bindValue(":name", name);

    if(!descriptionQuery.exec())
    {
        uint32_t sizeDesc = 0;
        m_sock.write(&sizeDesc, sizeof(uint32_t));
    }
    else
    {
        descriptionQuery.next();

        std::string description = descriptionQuery.value(0).toString().toStdString();
        uint32_t sizeDesc = description.size()+1;
        m_sock.write(&sizeDesc, sizeof(uint32_t));
        m_sock.write(description.c_str(), sizeDesc*sizeof(char));
    }
}

/**
 * @brief Récupère et écrit dans la base de donnée la liste des fichiers partagés du client.
 */
void ClientHandler::getFileList(int nbFiles)
{
    FileDescCli file;
    char *desc = NULL;

    for(int i =0; i<nbFiles; i++)
    {
        m_sock.read(&file, sizeof(FileDescCli));

        desc = new char[file.descSize+1];
        desc[file.descSize] = '\0';
        if(file.descSize > 0)
        {
            m_sock.read(desc, file.descSize);
        }
        SaveFile(file.name, desc, file.size);

        delete desc;
    }

}

/**
 * @brief Envoi les noms de fichiers reçut par le serveur, des différent client, moins ceux du client demandant.
 */
void ClientHandler::sendFileList()
{
    // On recupere les noms de fichiers dans la bd et on les envoies au client.
    QSqlQuery fileCountQuery = m_db.prepareQuery("SELECT COUNT(*) from files where username!=:username");
    QSqlQuery fileQuery = m_db.prepareQuery("SELECT name, username, size from files where username!=:username");

    fileCountQuery.bindValue(":username", m_username);
    fileQuery.bindValue(":username", m_username);

    if(!fileCountQuery.exec())
    {
        throw DBException("Error during file counting", m_db.lastError());
    }
    fileCountQuery.next();

    if(!fileQuery.exec())
    {
        throw DBException("Error during file accessing", m_db.lastError());
    }


    // On envoie le nombre de fichiers qui vont être envoyés au client
    Message message;
    message.type = 'E';
    message.content.nbFiles = fileCountQuery.value(0).toInt();

    m_sock.write(&message, sizeof(Message));

    // On envoie les fichiers
    while(fileQuery.next())
    {
        FileDescServ file;
        strcpy(file.name, fileQuery.value(0).toString().toStdString().c_str());
        strcpy(file.author, fileQuery.value(1).toString().toStdString().c_str());
        file.size = fileQuery.value(2).toInt();

        m_sock.write(&file, sizeof(FileDescServ));
    }
}

/**
 * @brief Vérifie le username password d'un utilisateur et renvoie vrai s'il est valide.
 */
bool ClientHandler::login(Identification id)
{
    QSqlQuery checkPasswordQuery = m_db.prepareQuery("SELECT password from identification where username=:username");

    checkPasswordQuery.bindValue(":username", id.username);

    if(!checkPasswordQuery.exec())
    {
        throw DBException("Error during password checking", m_db.lastError());
    }
    checkPasswordQuery.next();

    if (checkPasswordQuery.value(0).toString().compare(id.password)==0)
    {
        QSqlQuery loginQuery = m_db.prepareQuery("INSERT INTO connexion (username,ip, port) VALUES (:username, :ip, :port)");

        loginQuery.bindValue(":username", id.username);
        loginQuery.bindValue(":ip", m_ip);
        loginQuery.bindValue(":port", id.dlPort);
        if(!loginQuery.exec())
        {
            throw DBException("Error during login", m_db.lastError());
        }

        return true;
    }
    else
        return false;
}

/**
 * @brief Crée un utilisateur s'il n'existe pas et renvoie vrai si l'utilisateur à été créé.
 */
bool ClientHandler::createUser(Identification id)
{
    QSqlQuery existQuery = m_db.prepareQuery("SELECT password from identification where username=:username");

    existQuery.bindValue(":username", id.username);
    existQuery.exec();
    if(!existQuery.exec())
    {
        throw DBException("Error during existance checking", m_db.lastError());
    }
    existQuery.next();

    if(existQuery.isNull(0)) //si le compte n'existe pas
    {

        QSqlQuery createQuery = m_db.prepareQuery("INSERT INTO identification (username,password) VALUES (:username,:password)");

        createQuery.bindValue(":username", id.username);
        createQuery.bindValue(":password", id.password);
        if(!createQuery.exec())
        {
            throw DBException("Error during Checking", m_db.lastError());
        }

        return true;
    }
    else
    {
        return false;
    }
}

/**
 * @brief Enregistre la description d'un fichier envoyé par le client dans la base de données.
 */
void ClientHandler::SaveFile(const char* fileName, const char* fileDescription, int fileSize)
{
    QSqlQuery insertFileQuery = m_db.prepareQuery("INSERT INTO files(username, name, desc, size) VALUES(:username,:name,:desc,:size)" );

    insertFileQuery.bindValue(":username", m_username.toStdString().c_str());
    insertFileQuery.bindValue(":name", fileName);
    insertFileQuery.bindValue(":desc", fileDescription);
    insertFileQuery.bindValue(":size", fileSize);


    if(!insertFileQuery.exec())
    {
        throw DBException("Error during file insertion", m_db.lastError());
    }
}

/**
 * @brief Déconnecte un client.
 */
void ClientHandler::logout()
{
    /* suppression de la connection */
    QSqlQuery logoutQuery = m_db.prepareQuery("DELETE FROM connexion WHERE username=:username AND ip=:ip" );

    logoutQuery.bindValue(":username", m_username);
    logoutQuery.bindValue(":ip", m_ip);

    if(!logoutQuery.exec())
    {
        throw DBException("Error during client logout", m_db.lastError());
    }

    /* suppression des fichiers de l'utilisateur */
    QSqlQuery fileRemoveQuery = m_db.prepareQuery("DELETE FROM files WHERE username=:username" );

    fileRemoveQuery.bindValue(":username", m_username);

    if(!fileRemoveQuery.exec())
    {
        throw DBException("Error during deletion of client files", m_db.lastError());
    }
}
